package v1

import (
	"context"
	"time"
	"wxpay/internal/conf"
	"wxpay/internal/dao"
	"wxpay/internal/model"
	wxpay "wxpay/pkg/api/v1"
	"wxpay/pkg/db"
	"wxpay/pkg/mcontext"
	"wxpay/pkg/merrors"

	"github.com/golang/protobuf/ptypes/empty"

	"go.uber.org/zap"

	"github.com/go-redis/redis/v8"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// Server represents the gRPC server
type Server struct {
	d     *dao.WxpayDao
	redis *redis.Client
	//wxpay.UnimplementedWxpayServer
}

func NewWxpayServer(dsn string) (*Server, error) {
	var err error
	ctx := context.Background()
	logger := mcontext.Logger(ctx)
	l := &db.Logger{
		LogLevel:      db.Info,
		SlowThreshold: time.Second,
	}
	database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: l})
	if err != nil {
		logger.Fatal("could not init database", zap.Error(err))
	}
	sqlDB, err := database.DB()
	if err != nil {
		logger.Fatal("could not get sqlDB", zap.Error(err))
	}
	sqlDB.SetMaxIdleConns(conf.Conf.ORM.Idle)
	sqlDB.SetMaxOpenConns(conf.Conf.ORM.Active)
	sqlDB.SetConnMaxLifetime(conf.Conf.ORM.IdleTimeout)

	wxpayDao := dao.NewWxpayDao(database)
	redisClient := redis.NewClient(&redis.Options{
		Network:  "tcp",
		Addr:     conf.Conf.Redis.Addr,
		Password: conf.Conf.Redis.Password,
		DB:       conf.Conf.Redis.DB,
	})
	if err = registerConsumers(wxpayDao, redisClient); err != nil {
		logger.Fatal("could not register consumer", zap.Error(err))
	}

	return &Server{d: wxpayDao, redis: redisClient}, nil
}

func registerConsumers(wxpayDao *dao.WxpayDao, redis *redis.Client) error {
	channel := "wxpay"
	for _, topic := range model.ListenTopics {
		worker, err := getWorker(topic, wxpayDao, redis)
		if err != nil {
			return err
		}
		wxpayDao.StartWorker(topic, Consumer{worker}, channel)
	}
	return nil
}

func Close(ctx context.Context, ser *Server) {
	_ = ser.redis.Close()
	ser.d.Close(ctx)
}

func (s *Server) DummyHandler(ctx context.Context, _ *empty.Empty) (*wxpay.HealthCheckResp, error) {
	const op merrors.Op = "HealthCheck"
	_, err := s.redis.Ping(ctx).Result()
	if err != nil {
		return nil, merrors.E(op, merrors.Internal, merrors.ErrRedisClient, err.Error())
	}
	if err = s.d.Ping(ctx); err != nil {
		return nil, merrors.E(op, merrors.DBError, merrors.ErrDB, err.Error())
	}
	return &wxpay.HealthCheckResp{Message: "pong"}, nil
}
